在很早期 TypeScript 還沒建立出 impot
和 export
的時候,namespace
是一種組織代碼的方式。但隨著 ES6 模組系統的普及,其**namespace
** 的使用上有所下降。當然~命名空間在某些情境下仍然展現出其不可或缺的價值。那就先來一個一個介紹吧~
在撰寫小專案的過程中,很常會看到要 impot
和 export
,那這些是什麼東西呢?
ES6 (ES2015) 帶來了先進的模組系統(幾乎可以說經典的版本),其中就是可以開始使用 import
和 export
語法。此模組系統讓 JavaScript 開發者可以直接在瀏覽器和 Node.js 中使用模組,不需要第三方庫,如 CommonJS 或 AMD了。
基本語法:
Export: 要讓模組中的某個變數、函式或類別可以被其他模組使用,必須將它導出。
// myModule.ts
export const myVar = 10;
export function myFunction() {...}
Import: 使用其他模組的導出元素時,需要使用 import
來導入它。
// anotherModule.ts
import { myVar, myFunction } from './myModule';
**預設導出:**每個模組可以有一個預設導出。預設導出的元素可以使用任何名稱來導入。不過要注意的是,只能有一個預設導出,不能有兩個唷。
// math.ts
export default function add(x, y) {
return x + y;
}
// app.ts
import customName from './math';
namespace
)如文章一開始所說,在 TypeScript 的早期版本中,namespace
是一種組織代碼的方式。但隨著 ES6 模組系統的普及,其使用頻率有所下降。當然,命名空間在某些情境下仍然展現出其不可或缺的價值。命名空間不僅可以避免命名衝突,而且還提供了更有組織的程式碼結構,幫助我們將相似或相關的功能集中管理。它也為我們提供了一種封裝的方式,使得外部程式碼難以直接訪問命名空間內的成員。
當在一個大型專案中,使用多個套件是很正常的,不同套件中可能存在相同的類、函式或變數名稱,這會導致命名衝突。使用命名空間,可以將這些相關的功能放在不同的空間領域下,減少命名衝突的可能性。
namespace FirstPackage {
export class Demo {
// ...
}
}
namespace SecondPackage {
export class Demo {
// ...
}
}
TypeScript 支持將多個命名空間的宣告融合成同一個命名空間。這是一個非常強大的功能,允許開發者分散定義命名空間,但在編譯時它們會被合併為一個。(但是我怎麼想都覺得很神秘就是了XD,畢竟寫在不同的地方,希望哪天不會出事。)
namespace MergedNamespace {
export function func1() {
// ...
}
}
namespace MergedNamespace {
export function func2() {
// ...
}
}
// 使用時,MergedNamespace 將包含 func1 和 func2
在命名空間內部,我們還可以定義其他命名空間,這提供了更多的組織代碼的選項。
namespace ParentNamespace {
export namespace ChildNamespace {
export function nestedFunc() {
// ...
}
}
}
// 使用時需透過完整的命名空間路徑
ParentNamespace.ChildNamespace.nestedFunc();
總結而言,儘管 ES6 的模組系統在現代開發中廣受歡迎,但命名空間在某些情境下仍然很有用,特別是在需要額外的封裝或組織複雜程式碼的時候。
TypeScript 提供的型別檢查系統非常的棒,但實際上許多現有的 JavaScript 套件和框架尚未遷移到 TypeScript(但現在2023年了,基本上應該沒有什麼還沒遷移)。那時,TypeScript 提供了一個方便的橋梁,可以在 TypeScript 中利用這些 JavaScript 庫,而這方法就是型別宣告 (Type Declaration)。(還真別說,我真的非常好奇為啥這樣取名)
當你在 TypeScript 專案中直接引入一個 JavaScript 的函式庫或模組時,TypeScript 編譯器會報錯,因為它不知道這個模組的型別定義。例如:
import * from 'test'; // 純 JavaScript 檔案
如果 test.js 沒有型別定義,TypeScript 就會報錯。
為了解決這個問題,TypeScript 引入了 .d.ts
檔案,也稱為型別定義檔。這些檔案不包含具體的實作,而只描述了 JavaScript 模組的 API 的型別宣告。
例如,對於前面的 test 套件,可以看看有沒有機會使用 npm 來安裝它的型別定義:
npm install --save-dev @types/test
一旦安裝了型別定義,就可以很爽的直接 TypeScript 中使用 test,而 TypeScript 也能為提供正確的型別檢查和 IntelliSense。
在大部分情況下,可能沒有型別定義檔。這時,可以自己為 JavaScript 模組建立一個。假設我有以下 JavaScript 模組:
// myModule.js
module.exports = {
greet: function(name) {
return 'Hello, ' + name;
}
};
就可以為其創建以下型別定義檔:
// myModule.d.ts
declare module 'myModule' {
export function greet(name: string): string;
}
這樣,在 TypeScript 中引入 myModule
時,它就知道如何處理 greet
函數的型別了。
總結:型別宣告提供了一種方法,允許在 TypeScript 中安全地使用現有的 JavaScript 套件,不需要重寫它們。這大大提高了 TypeScript 的可用性和實用性,並促使更多的開發者遷移。
當 TypeScript 專案中使用 JavaScript 套件,這通常意味著該套件可能不包含 TypeScript 的型別資訊。以下是在 TypeScript 中處理 JavaScript 套件的基本流程:
在許多情況下,第三方的 TypeScript 開發者可能已經為這些 JavaScript 套件提供了型別定義檔。這些檔案通常都在 DefinitelyTyped 專案中,並且可以透過 npm 安裝。例如,如果想要在 TypeScript 專案中使用 jQuery,可以這麼做:
npm install --save jquery
npm install --save-dev @types/jquery
這樣,就完成了為 jQuery 安裝了型別定義。
如果找不到已存在的型別定義,那麼可能需要自己手動為這些模組建立型別定義。這是一個稍微複雜的過程,但只要了解基本的 TypeScript 語法,這也不算太難。直接開始創建一個 .d.ts
檔案來宣告模組的型別,並在我的 TypeScript 設定中引用它。
any
逃避型別檢查(大絕招)如果只是想快速使用一個套件而不考慮型別,可以直接使用 any
類型來告訴 TypeScript 忽略型別檢查。但請務必注意,將直接失去使用 TypeScript 的優點。
declare module 'some-js-library' {
var exports: any;
export = exports;
}
當然,這絕不是最佳方法,因為已經失去 TypeScript 帶來的型別安全性。